package jduck.ui;

import static java.awt.event.KeyEvent.*;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JLabel;

public class Script {
	private enum Etype {
		mouse, keyboard, sleep, set
	};

	public static class EventCmd {
		public EventCmd(Etype etype, List<Integer> events) {
			super();
			this.etype = etype;
			this.events = events;
		}

		private Etype etype;
		private List<Integer> events;

		public Etype getEtype() {
			return etype;
		}

		public List<Integer> getEvents() {
			return events;
		}
	}

	private List<EventCmd> commands;
	private Robot robot;
	private Random ran;
	private int stddelay = 10;
	private int stdran = 10;

	//	public Script(String script) throws AWTException {
	//		commands = new ArrayList<EventCmd>();
	//		robot = new Robot();
	//		ran = new Random();
	//
	//		boolean inBracket = false;
	//		boolean escape = false;
	//		StringBuilder cmd = new StringBuilder();
	//		for (char c : script.toCharArray()) {
	//			if (inBracket) {
	//				if (c == '}') {
	//					inBracket = false;
	//					commands.add(commandFromString(cmd.toString()));
	//					cmd.setLength(0);
	//				} else if (c == '{')
	//					throw new IllegalArgumentException("nested brackets in command sequence: " + script);
	//				else
	//					cmd.append(c);
	//			} else {
	//				if (c == '{')
	//					inBracket = true;
	//				else
	//					commands.add(createEventCmd(c));
	//			}
	//		}
	//		if (commands.size() <= 0)
	//			throw new RuntimeException("Script empty or poorly ended");
	//	}

	//	public Script(String script) throws AWTException {
	//		commands = new ArrayList<EventCmd>();
	//		robot = new Robot();
	//		ran = new Random();
	//
	//		boolean inQuote = false;
	//		boolean escape = false;
	//		StringBuilder cmd = new StringBuilder();
	//		for (char c : script.toCharArray()) {
	//			switch (c) {
	//			case '\'':
	//				if (!inQuote) {
	//					inQuote = true;
	//				} else {
	//					commands.add(createEventCmd(c));
	//				}
	//				break;
	//			case ';':
	//			case ' ':
	//				if (cmd.length() > 0) {
	//					commands.add(commandFromString(cmd.toString()));
	//					cmd.setLength(0);
	//				}
	//				break;
	//			default:
	//				cmd.append(c);
	//				break;
	//			}
	//		}
	//		if (commands.size() <= 0)
	//			throw new RuntimeException("Script empty or poorly ended");
	//	}

	public Script(String script) throws AWTException {
		commands = new ArrayList<EventCmd>();
		robot = new Robot();
		robot.setAutoWaitForIdle(true);
		ran = new Random();

		script=removeCommented(script);
		
		boolean inQuote = false;
		boolean escape = false;
		StringBuilder cmd = new StringBuilder();
		for (char c : script.toCharArray()) {
			if (inQuote) {
				if (c == '\'')
					inQuote = false;
				else
					commands.add(createEventCmd(c));
			} else {
				if (c == '\'')
					inQuote = true;
				else {
					if (c == ' ' || c == ';') {
						if (cmd.length() > 0) {
							commands.add(commandFromString(cmd.toString()));
							cmd.setLength(0);
						}
					} else
						cmd.append(c);
				}
			}
		}
		if (!inQuote)
			if (cmd.length() > 0)
				commands.add(commandFromString(cmd.toString()));
		if (commands.size() <= 0)
			throw new RuntimeException("Script empty or poorly ended");
	}
	private static String removeCommented(String script) {
		char[] charRay=new char[script.length()];
		boolean inCom1=false;
		int index=0;
		for(char c: script.toCharArray()) {
			if ( inCom1 ) {
				if ( c=='\\' )
					inCom1=false;
			} else {
				if ( c=='/' )
					inCom1=true;
				else
					charRay[index++] = c;
			}
		}
		String s = new String(charRay,0,index);
		return s;
	}
	public void execute(long start, long stoptime, JLabel label) throws InterruptedException {
		try {
			long lastcheck = System.currentTimeMillis();
			int step=0;
			for (EventCmd cmd : commands) {
				step++;
				long now = System.currentTimeMillis();
				if ( stoptime <= now )
					return;
				if ( now - lastcheck > 500L && label!=null) {
					label.setText("<html>Remaining time: " + Clicker.longSpanToStringShort(stoptime-now) + 
							"<br>Step: " + step + "/" + commands.size() + "</html>");
				}
					
				
				switch (cmd.getEtype()) {
				case keyboard:
					for (Integer keycode : cmd.events) {
						robot.keyPress(keycode);
						//						robot.waitForIdle();
					}
					for (Integer keycode : cmd.events) {
						robot.keyRelease(keycode);
						//						robot.waitForIdle();
					}
					//					Thread.sleep(ran.nextInt(stdran) + stddelay);
					break;
				case mouse:
					robot.mousePress(cmd.getEvents().get(0));
					//					robot.waitForIdle();
					robot.mouseRelease(cmd.getEvents().get(0));
					//					robot.waitForIdle();
					break;
				case sleep:
					if (cmd.getEvents().get(1) > 0)
						Thread.sleep(cmd.getEvents().get(0) + ran.nextInt(cmd.getEvents().get(1)));
					else
						Thread.sleep(cmd.getEvents().get(0));
					robot.waitForIdle();
					break;
				case set:
					stddelay = cmd.getEvents().get(0);
					stdran = cmd.getEvents().get(1);
				}
				//				robot.waitForIdle();
				if (cmd.getEtype() != Etype.set)
					if (stdran > 0)
						Thread.sleep(ran.nextInt(stdran) + stddelay);
					else
						Thread.sleep(stddelay);
			}
		} finally {
		}
	}

	private EventCmd commandFromString(String cmd) {
		cmd = cmd.toUpperCase();

		if (cmd.startsWith("SLEEP")) {
			String[] args = cmd.split(",");
			if (args.length != 3)
				throw new IllegalArgumentException(
						"SLEEP commands has illegal arguments (must have 2 - SLEEP,.5,.05 for instance): " + cmd);
			int p1 = (int) (Double.parseDouble(args[1]) * 1000);
			int p2 = (int) (Double.parseDouble(args[2]) * 1000);
			return createSleepCmd(p1, p2);
		} else if (cmd.startsWith("DELAY")) {
			String[] args = cmd.split(",");
			if (args.length != 3)
				throw new IllegalArgumentException(
						"DELAY commands has illegal arguments (must have 2 - DELAY,.5,.04 for instance): " + cmd);
			int p1 = (int) (Double.parseDouble(args[1]) * 1000);
			int p2 = (int) (Double.parseDouble(args[2]) * 1000);
			return createSetCmd(p1, p2);
		} else {
			switch (cmd) {
			case "HOME":
				return createEventSeq(VK_HOME);
			case "END":
				return createEventSeq(VK_END);
			case "PD":
				return createEventSeq(VK_PAGE_DOWN);
			case "PU":
				return createEventSeq(VK_PAGE_UP);
			case "UP":
				return createEventSeq(VK_UP);
			case "DN":
				return createEventSeq(VK_DOWN);
			case "L":
				return createEventSeq(VK_LEFT);
			case "R":
				return createEventSeq(VK_RIGHT);
			case "ENT":
				return createEventSeq(VK_ENTER);
			case "M1":
				return createMouseCmd(InputEvent.BUTTON1_DOWN_MASK);
			case "M2":
				return createMouseCmd(InputEvent.BUTTON2_DOWN_MASK);
			case "M3":
				return createMouseCmd(InputEvent.BUTTON3_DOWN_MASK);
			default:
				throw new IllegalArgumentException("Cannot interpret command string " + cmd);
			}
		}
	}

	private EventCmd createMouseCmd(int mask) {
		ArrayList<Integer> keySeq = new ArrayList<Integer>();
		keySeq.add(mask);
		return new EventCmd(Etype.mouse, keySeq);
	}

	private EventCmd createSleepCmd(int sleeptime, int random) {
		ArrayList<Integer> sleepSeq = new ArrayList<Integer>();
		sleepSeq.add(sleeptime);
		sleepSeq.add(random);
		return new EventCmd(Etype.sleep, sleepSeq);
	}

	private EventCmd createSetCmd(int keydelay, int random) {
		ArrayList<Integer> setSeq = new ArrayList<Integer>();
		setSeq.add(keydelay);
		setSeq.add(random);
		return new EventCmd(Etype.set, setSeq);
	}

	public EventCmd createEventCmd(char character) {
		switch (character) {
		case 'a':
			return createEventSeq(VK_A);
		case 'b':
			return createEventSeq(VK_B);
		case 'c':
			return createEventSeq(VK_C);
		case 'd':
			return createEventSeq(VK_D);
		case 'e':
			return createEventSeq(VK_E);
		case 'f':
			return createEventSeq(VK_F);
		case 'g':
			return createEventSeq(VK_G);
		case 'h':
			return createEventSeq(VK_H);
		case 'i':
			return createEventSeq(VK_I);
		case 'j':
			return createEventSeq(VK_J);
		case 'k':
			return createEventSeq(VK_K);
		case 'l':
			return createEventSeq(VK_L);
		case 'm':
			return createEventSeq(VK_M);
		case 'n':
			return createEventSeq(VK_N);
		case 'o':
			return createEventSeq(VK_O);
		case 'p':
			return createEventSeq(VK_P);
		case 'q':
			return createEventSeq(VK_Q);
		case 'r':
			return createEventSeq(VK_R);
		case 's':
			return createEventSeq(VK_S);
		case 't':
			return createEventSeq(VK_T);
		case 'u':
			return createEventSeq(VK_U);
		case 'v':
			return createEventSeq(VK_V);
		case 'w':
			return createEventSeq(VK_W);
		case 'x':
			return createEventSeq(VK_X);
		case 'y':
			return createEventSeq(VK_Y);
		case 'z':
			return createEventSeq(VK_Z);
		case 'A':
			return createEventSeq(VK_SHIFT, VK_A);
		case 'B':
			return createEventSeq(VK_SHIFT, VK_B);
		case 'C':
			return createEventSeq(VK_SHIFT, VK_C);
		case 'D':
			return createEventSeq(VK_SHIFT, VK_D);
		case 'E':
			return createEventSeq(VK_SHIFT, VK_E);
		case 'F':
			return createEventSeq(VK_SHIFT, VK_F);
		case 'G':
			return createEventSeq(VK_SHIFT, VK_G);
		case 'H':
			return createEventSeq(VK_SHIFT, VK_H);
		case 'I':
			return createEventSeq(VK_SHIFT, VK_I);
		case 'J':
			return createEventSeq(VK_SHIFT, VK_J);
		case 'K':
			return createEventSeq(VK_SHIFT, VK_K);
		case 'L':
			return createEventSeq(VK_SHIFT, VK_L);
		case 'M':
			return createEventSeq(VK_SHIFT, VK_M);
		case 'N':
			return createEventSeq(VK_SHIFT, VK_N);
		case 'O':
			return createEventSeq(VK_SHIFT, VK_O);
		case 'P':
			return createEventSeq(VK_SHIFT, VK_P);
		case 'Q':
			return createEventSeq(VK_SHIFT, VK_Q);
		case 'R':
			return createEventSeq(VK_SHIFT, VK_R);
		case 'S':
			return createEventSeq(VK_SHIFT, VK_S);
		case 'T':
			return createEventSeq(VK_SHIFT, VK_T);
		case 'U':
			return createEventSeq(VK_SHIFT, VK_U);
		case 'V':
			return createEventSeq(VK_SHIFT, VK_V);
		case 'W':
			return createEventSeq(VK_SHIFT, VK_W);
		case 'X':
			return createEventSeq(VK_SHIFT, VK_X);
		case 'Y':
			return createEventSeq(VK_SHIFT, VK_Y);
		case 'Z':
			return createEventSeq(VK_SHIFT, VK_Z);
		case '`':
			return createEventSeq(VK_BACK_QUOTE);
		case '0':
			return createEventSeq(VK_0);
		case '1':
			return createEventSeq(VK_1);
		case '2':
			return createEventSeq(VK_2);
		case '3':
			return createEventSeq(VK_3);
		case '4':
			return createEventSeq(VK_4);
		case '5':
			return createEventSeq(VK_5);
		case '6':
			return createEventSeq(VK_6);
		case '7':
			return createEventSeq(VK_7);
		case '8':
			return createEventSeq(VK_8);
		case '9':
			return createEventSeq(VK_9);
		case '-':
			return createEventSeq(VK_MINUS);
		case '=':
			return createEventSeq(VK_EQUALS);
		case '~':
			return createEventSeq(VK_SHIFT, VK_BACK_QUOTE);
		case '!':
			return createEventSeq(VK_EXCLAMATION_MARK);
		case '@':
			return createEventSeq(VK_AT);
		case '#':
			return createEventSeq(VK_NUMBER_SIGN);
		case '$':
			return createEventSeq(VK_DOLLAR);
		case '%':
			return createEventSeq(VK_SHIFT, VK_5);
		case '^':
			return createEventSeq(VK_CIRCUMFLEX);
		case '&':
			return createEventSeq(VK_AMPERSAND);
		case '*':
			return createEventSeq(VK_ASTERISK);
		case '(':
			return createEventSeq(VK_LEFT_PARENTHESIS);
		case ')':
			return createEventSeq(VK_RIGHT_PARENTHESIS);
		case '_':
			return createEventSeq(VK_UNDERSCORE);
		case '+':
			return createEventSeq(VK_PLUS);
		case '\t':
			return createEventSeq(VK_TAB);
		case '\n':
			return createEventSeq(VK_ENTER);
		case '[':
			return createEventSeq(VK_OPEN_BRACKET);
		case ']':
			return createEventSeq(VK_CLOSE_BRACKET);
		case '\\':
			return createEventSeq(VK_BACK_SLASH);
		case '{':
			return createEventSeq(VK_SHIFT, VK_OPEN_BRACKET);
		case '}':
			return createEventSeq(VK_SHIFT, VK_CLOSE_BRACKET);
		case '|':
			return createEventSeq(VK_SHIFT, VK_BACK_SLASH);
		case ';':
			return createEventSeq(VK_SEMICOLON);
		case ':':
			return createEventSeq(VK_COLON);
		case '\'':
			return createEventSeq(VK_QUOTE);
		case '"':
			return createEventSeq(VK_QUOTEDBL);
		case ',':
			return createEventSeq(VK_COMMA);
		case '<':
			return createEventSeq(VK_LESS);
		case '.':
			return createEventSeq(VK_PERIOD);
		case '>':
			return createEventSeq(VK_GREATER);
		case '/':
			return createEventSeq(VK_SLASH);
		case '?':
			return createEventSeq(VK_SHIFT, VK_SLASH);
		case ' ':
			return createEventSeq(VK_SPACE);
		default:
			throw new IllegalArgumentException("Cannot type character " + character);
		}
	}

	private EventCmd createEventSeq(int... keyCodes) {
		ArrayList<Integer> keySeq = new ArrayList<Integer>();
		for (int i = 0; i < keyCodes.length; i++) {
			keySeq.add(keyCodes[i]);
		}
		return new EventCmd(Etype.keyboard, keySeq);
	}

	public static void main(String[] args) {
		try {
			// Script script = new
			// Script("{SLEEP 1000}sleeping{SLEEP 5000}mouse{M1}abc{PU}{PU}{HOME}{HOME}{END}{END}{HOME}{HOME}hello");
//			Script script = new Script("delay,0,1 m1; 'typing here' m1 Sleep,2,.4 DelaY,0,0 'later type'");
//			long now = System.currentTimeMillis();
//			script.execute(now, now + 60000L, null);
			
			String s = "//this is a test/ to see \\ what is removed /inside\\ comments";
			System.out.println("s: " + s + "\nout: " + removeCommented(s));
		} catch (Exception e) {
			e.printStackTrace();
		}
		// typing herelater type
	}
}
